import sys
import platform
import os
import zmq
import asyncio
import tornado.ioloop
import tornado.httpserver
import tornado.web

from log import Log
from models import auths
import config

if sys.version_info.major >= 3 and sys.version_info.minor >= 8:
    if platform.system() == "Windows":
        asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())


spawn_ip = config.interpreter["ip"]
spawn_port = config.interpreter["port"]

context = zmq.Context()
socket = context.socket(zmq.DEALER)
socket.connect("tcp://%s:%d" % (spawn_ip, spawn_port))

server_min_port = config.options["server_port"]
server_max_port = config.options["server_max_port"]
manager = {}


class Task:
    def __init__(self, port, kernel_ip, kernel_port):
        self.logger = Log().logger
        self.kernel_ip = kernel_ip
        self.kernel_port = kernel_port
        self.port = port
        self.proc = None
        self.future = None

    async def run(self):
        global manager
        if self.port not in manager:
            cmd = "python %s --port=%i --kernel_ip=%s --kernel_port=%i" % (os.path.join(config.BASE_DIRS, "server.py"),
                                                                           self.port, self.kernel_ip, self.kernel_port)
            self.proc = await asyncio.create_subprocess_shell(cmd,
                                                              stdout=asyncio.subprocess.PIPE,
                                                              stderr=asyncio.subprocess.PIPE)
            manager[str(self.port)] = self
            stdout, stderr = await self.proc.communicate()
            self.logger.info(f'[{cmd!r} exited with {self.proc.returncode}]')
            if stdout:
                self.logger.info(f'[stdout]\n{stdout.decode()}')
            if stderr:
                self.logger.error(f'[stderr]\n{stderr.decode()}', exc_info=False)
        else:
            pass


class CreateHandler(tornado.web.RequestHandler):
    port = server_min_port

    def __init__(self, application, request):
        super().__init__(application, request)

    def set_default_headers(self):
        self.set_header("Access-Control-Allow-Origin", "*")  # 这个地方可以写域名
        self.set_header("Access-Control-Allow-Headers", "x-requested-with")
        self.set_header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS')

    def options(self):
        # no body
        self.set_status(204)
        self.finish()

    async def get(self):
        res = {
            "code": 0,
            "msg": "",
            "data": None
        }
        token = self.get_argument(name="token", default=None)
        if token is None:
            res["code"] = 1
            res["msg"] = "token为空"
        else:
            user_id = auths.get_user_id(token)
            if user_id is None:
                res["code"] = 2
                res["msg"] = "token无效"
            else:
                port = await self.popen()
                if port is None:
                    res["code"] = 3
                    res["msg"] = "端口资源用尽"
                else:
                    res["data"] = port
        self.write(res)

    def get_port(self):
        if CreateHandler.port < server_max_port:
            pk = CreateHandler.port
            CreateHandler.port = CreateHandler.port + 1
            return pk
        else:
            for pk in range(server_min_port, server_max_port):
                if str(pk) in manager:
                    continue
                else:
                    return pk
        return None

    async def popen(self):
        port = self.get_port()
        if port is not None:
            if socket is not None:
                try:
                    socket.send(b"open")
                    ret = socket.recv_multipart()
                    kernel_ip = ret.pop(0).decode("utf-8")
                    if kernel_ip == "端口资源用尽":
                        return None
                    kernel_port = int(ret.pop(0))
                except KeyboardInterrupt:
                    raise
                asyncio.ensure_future(Task(port, kernel_ip, kernel_port).run())
            else:
                pass
            await asyncio.sleep(1)
            return port
        else:
            return None


class CloseHandler(tornado.web.RequestHandler):
    def __init__(self, application, request):
        super().__init__(application, request)

    def get(self):
        port = self.get_argument("port", None)
        kernel_ip = self.get_argument("kernel_ip", None)
        kernel_port = self.get_argument("kernel_port", None)
        if port is not None and kernel_ip is not None and kernel_port is not None:
            if port in manager:
                proc = getattr(manager[port], "proc", None)
                if proc is not None:
                    proc.kill()
                    del manager[port]
                    try:
                        if socket is not None:
                            try:
                                socket.send_multipart([b"close", kernel_ip.encode("utf-8"), b"%d" % int(kernel_port)])
                            except KeyboardInterrupt:
                                raise
                        else:
                            pass
                    except zmq.ZMQError as e:
                        if e.errno == zmq.ETERM:
                            pass
                        else:
                            raise
                else:
                    pass
            else:
                pass
        else:
            pass


if __name__ == "__main__":
    try:
        logger = Log().logger
        logger.info(f"default start by port {config.options['default_port']}")
        app = tornado.web.Application([
            (r"/create", CreateHandler),
            (r"/close", CloseHandler)
        ])
        httpServer = tornado.httpserver.HTTPServer(app)
        httpServer.listen(config.options["default_port"])
        tornado.ioloop.IOLoop.current().start()
    except KeyboardInterrupt:
        socket.close()
        context.destroy()
